﻿using System;
using System.Drawing;
using System.Windows.Forms;
using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Eco.Services;
using Eco.ObjectRepresentation;
using Eco.ObjectImplementation;
using Eco.UmlRt;
using Eco.UmlCodeAttributes;
using Eco.Subscription;

namespace CommandGraph.Model
{
  /// <summary>
  /// Класс, реализующий копирование элементов графа
  /// </summary>
  public class GraphCopyist
  {
    private mutable serviceProvider : IEcoServiceProvider = null;
    private mutable parentControl : Control = null;
    private mutable toolBox : ToolBox = null;
    private mutable clipboard : list[GraphEntityBase] = null;
    private mutable mouseData : MouseEventArgs = null;
    private mutable graph : Graph = null;
    
    public this(serviceProvider : IEcoServiceProvider, 
        parentControl : Control,
        toolBox : ToolBox,
        graph : Graph)
    {
      when (serviceProvider == null)
        throw NullReferenceException("ServiceProvider cannot be null!");
      when (parentControl == null)
        throw NullReferenceException("ParentControl cannot be null!");
      when (toolBox == null)
        throw NullReferenceException("ToolBox cannot be null!");
      when (graph == null)
        throw NullReferenceException("Graph cannot be null!");
            
      this.serviceProvider = serviceProvider;
      this.parentControl = parentControl;
      this.toolBox = toolBox;
      this.graph = graph;
      //this.allEntities = graph.Enities.ToList();
        
      parentControl.KeyDown += parentControl_KeyDown;
      parentControl.MouseDown += parentControl_MouseDown;
    }
     
    private parentControl_KeyDown(_ : object, e : KeyEventArgs) : void
    {
      when (e.Control)
      {
        match (e.KeyCode)
        {
          | Keys.C => CopySelection()
          | Keys.V => Paste()
          | Keys.X => Crop()
          | _ => ()
        }
      }
    }
    
    private parentControl_MouseDown(_ : object, e : MouseEventArgs) : void
    {
      this.mouseData = e;
    }
    
    private CopyEntities(entitiesToCopy: list[GraphEntityBase], allEntities : list[GraphEntityBase]) 
      : list[GraphEntityBase]
    {
      def Copy(entities : list[GraphEntityBase])
        {
          def Duplicate(entities) : list[GraphEntityBase * GraphEntityBase]
          {
            | null :: tail => Duplicate(tail)
            | entity :: tail => (entity, entity.Clone()) :: Duplicate(tail)
            | [] => []
          }
        
          def RestoreConnections(all : list[GraphEntityBase * GraphEntityBase])
          {
            def searcher(sample, a, _){GraphEntityBase.ReferenceEquals(a, sample)};
          
            def CopyParentLink(original)
            {
              def pair = all.Find(searcher(original.Parent, _, _));
              match(pair)
              {
                | None => null
                | Some => 
                {
                    def (_, b) = pair.Value;
                    b
                }
              }
            }
          
            def CopyConnectionsCV(original, duplicated)
            {
              duplicated.Parent = CopyParentLink(original);
              foreach(child in original.NestedEntities)
              {
                def pair = all.Find(searcher(child, _, _));
                match(pair)
                {
                  | None => {}
                  | Some => 
                  {
                      def (_, entity) = pair.Value;
                      duplicated.NestedEntities.Add(entity);
                  }
                }
              }
              duplicated
            }
            def CopyConnectionsV(original, duplicated)
            {
              duplicated.Parent = CopyParentLink(original);
              duplicated
            }
            def CopyConnectionsIP(original, duplicated)
            {
              duplicated.Parent = CopyParentLink(original);
              duplicated
            }
            def CopyConnectionsAP(original, duplicated)
            {
              duplicated.Parent = CopyParentLink(original);
            
              match(all.Find(searcher(original.AttachedTransition, _, _)))
              {
                | Some => duplicated.Parent.AttachAnchorPoint(duplicated);
                | None => {}
              }

              duplicated
            }
            def CopyConnectionsT(original, duplicated)
            {
              def ConnectAnchor(pair)
              {
                | None => {}
                | Some => 
                {
                    def (anchorSource, anchorDuplicate) = pair.Value;
                  //определяем, к какому концу подсоединять
                    if (AnchorPoint.ReferenceEquals(original.Beginning, anchorSource))
                      duplicated.Beginning = anchorDuplicate :> AnchorPoint;
                    else
                      duplicated.Ending = anchorDuplicate :> AnchorPoint;
                }
              };
              def ConnectInflection(inflectionSource)
              {
                def pair = all.Find(searcher(inflectionSource, _, _));
                match(pair)
                {
                  | Some => 
                    {
                      def inflectionCopy = pair.Value[1] :> InflectionPoint;
                      duplicated.AddInflectionPoint(inflectionCopy);
                      inflectionCopy.ParentTransition = duplicated;
                    }
                  | None => {}
                }
              }
              duplicated.Parent = CopyParentLink(original);
              
              //концы перехода
              //копируем Beginning
              ConnectAnchor(all.Find(searcher(original.Beginning, _, _)));
              //копируем Ending
              ConnectAnchor(all.Find(searcher(original.Ending, _, _)));
              
              //точки перегиба
              original.InflectionPoints.Iter(ConnectInflection);
              
              duplicated
            }
            def CopyConnectionsLC(original, duplicated)
            {
              def RestoreBodyConn(original)
              {
                def pair = all.Find(searcher(original, _, _));
                match(pair)
                {
                  | None => null
                  | Some =>  pair.Value[1];
                }
              }
              
              duplicated.Parent = CopyParentLink(original);
              //восстанавливаем ссыль на тело цикла
              def pair = all.Find(searcher(original.LoopBody, _, _));
              match(pair)
              {
                | None => {}
                | Some =>  duplicated.LoopBody = (pair.Value[1] :> LoopBody);
              }
              //нужно восстановить ссыли на связи с телом цикла
              duplicated.ToBody = RestoreBodyConn(original.ToBody) :> ActiveTransition;
              duplicated.FromBody = RestoreBodyConn(original.FromBody) :> ActiveTransition;
              
              duplicated
            }
            def Reconnect(pairs)
            {
              | (original, duplicated) :: tail => 
                {
                  (match (original, duplicated)
                     {                       
                       | _ when original is CompositeVertex  => 
                           CopyConnectionsCV(original :> CompositeVertex, 
                                               duplicated :> CompositeVertex)
                       | _ when original is LoopCondition =>
                           CopyConnectionsLC(original :> LoopCondition, 
                                               duplicated :> LoopCondition)
                       | _ when original is Vertex  => 
                           CopyConnectionsV(original :> Vertex,
                                              duplicated :> Vertex)
                       | _ when original is InflectionPoint => 
                           CopyConnectionsIP(original :> InflectionPoint,
                                               duplicated :> InflectionPoint)
                       | _ when original is AnchorPoint => 
                           CopyConnectionsAP(original :> AnchorPoint,
                                               duplicated :> AnchorPoint)
                       | _ when original is Transition => 
                           CopyConnectionsT(original :> Transition,
                                              duplicated :> Transition)
                     }) :: Reconnect(tail)
                }
              | [] => []
            }
          
            Reconnect(all)
          }
        
          RestoreConnections(Duplicate(entities))
        }
        
      //т.к. при копировании цикл может быть не выделен полностью, то нужно добавить невыделенные элементы
      def entitiesToCopy = entitiesToCopy.Append(entitiesToCopy.Fold([], 
        fun(entity, acc)
        {
          acc.Append(match(entity)
          {
            | _ when entity is LoopBody =>
              {
                mutable res = [];
                def body = entity :> LoopBody;
                when (!entitiesToCopy.Contains(body.LoopCondition))
                  res ::= body.LoopCondition;
                when (!entitiesToCopy.Contains(body.LoopCondition.ToBody))
                  res ::= body.LoopCondition.ToBody;
                when (!entitiesToCopy.Contains(body.LoopCondition.FromBody))
                  res ::= body.LoopCondition.FromBody;
                res
              }
            | _ when entity is LoopCondition =>
              {
                mutable res = [];
                def cond = entity :> LoopCondition;
                when (!entitiesToCopy.Contains(cond.LoopBody))
                {
                  res ::= cond.LoopBody;
                  when (!entitiesToCopy.Contains(cond.ToBody))
                    res ::= cond.ToBody;
                  when (!entitiesToCopy.Contains(cond.FromBody))
                    res ::= cond.FromBody;
                }
                res
              }
            | _ => []
          })
        }));
      
      //т.к. точки привязки нельзя выделить, то мы выбираем из всех элементов некоторые точки привязки
      def anchorsToCopy = allEntities
        .Filter(fun(entity) 
          {(entity is AnchorPoint)
             && (!entitiesToCopy.Contains(entity))
             && (entitiesToCopy.Contains(entity.Parent))
             && (entitiesToCopy.Contains((entity :> AnchorPoint).AttachedTransition))});
      //выкидываем лишние переходы (концы которых не копировались) и добавляем сюда точки привязки
      def entitiesToCopy = entitiesToCopy.Append(anchorsToCopy);
      def entitiesToCopy = entitiesToCopy
        .Filter(fun (entity)
          {if (entity is Transition)
             entitiesToCopy.Contains((entity :> Transition).Beginning)
             && entitiesToCopy.Contains((entity :> Transition).Ending)
           else
             true});
      def copiedEntities = Copy(entitiesToCopy);
      //перед возвратом в нужно отфильтровать лишние точки привязки
      copiedEntities
        .Filter(fun(entity)
          {if (entity != null)
             if (entity is AnchorPoint)
               (entity :> AnchorPoint).AttachedTransition != null
             else 
               true
           else
             false});
    }
        
    private CopySelection() : void
    {
      def allEntities = this.graph.Entities.ToList();
      clipboard = CopyEntities((this.toolBox.GetToolByName("SelectionTool") :> SelectionTool)
        .SelectedEntities
        .ToList(), allEntities);
    }
    
    private Paste() : void
    {
      def manager = this.toolBox.GetToolByName("EntityManager") :> EntityManager;
      def selection = this.toolBox.GetToolByName("SelectionTool") :> SelectionTool;
      //элемент графа, в который будут вложены вставляемые элементы
      def parent = (match(selection)
                   {
                     | _ when (selection.SelectedEntities.Count == 1) && 
                         (selection.SelectedEntities[0] is CompositeVertex) => 
                           selection.SelectedEntities[0] :> CompositeVertex
                     | _ when (selection.SelectedEntities.Count == 0)
                     | _ => null;
                   });

      //получить суммарную область на экране, которую занимают указанные эл-ты графа
      def SummaryRegion(entities)
      {
        | entity :: tail => 
          {
            def region = SummaryRegion(tail);
            region.Union(entity.Region);
            region
          }
        | [] => Region(Rectangle.Empty)
      }
      //получить середину прямоугольника
      def RectangleCenter(rectangle)
      {
        Point(((rectangle.Left :> int) + (rectangle.Right :> int)) / 2,
          ((rectangle.Top :> int) + (rectangle.Bottom :> int)) / 2)
      }           
      
      def rectcenter = RectangleCenter(SummaryRegion(clipboard).GetBounds(this.parentControl.CreateGraphics()));
      //получаем вектор, на который должны быть смещены эл-ты при вставке
      def delta = Point(this.mouseData.X - rectcenter.X,
                          this.mouseData.Y - rectcenter.Y);
      
      //вставить указанные элементы
      def PasteEntities(entities)
      {
        | entity :: tail => 
          {
            when (entity.Parent == null)
            {
              entity.Parent = parent;
              when (parent != null)
                parent.NestedEntities.Add(entity);
              when (entity is MovableEntity)
                (entity :> MovableEntity).MoveBy(delta);
            }
            
            manager.AddEntity(entity);
            selection.AddEntity(entity);
            
            PasteEntities(tail);
          }
        | null
        | [] => {}
      }

      selection.Clear();
      /*def entitiesToPaste = ;
      entitiesToPaste
        .Iter(fun(entity)
          {
            when((entity is MovableEntity) && (entity.Parent == null))
              (entity :> MovableEntity).MoveBy(delta);
          });*/
      PasteEntities(CopyEntities(clipboard, clipboard));
      
      when(parent != null)
        parent.Painter.InvalidateEntity(parent);
    }
    
    private Crop() : void
    {
      def manager = this.toolBox.GetToolByName("EntityManager") :> EntityManager;
      def selection = this.toolBox.GetToolByName("SelectionTool") :> SelectionTool;
     
      def DeleteAll(entities)
      {
        | entity :: tail => 
          {
            manager.RemoveEntity(entity);
            DeleteAll(tail);
          }
        | [] => {}
      }
      
      CopySelection();
      DeleteAll(selection.SelectedEntities.ToList())
    } 
  }
}
